/****************************************************************************

	COPYRIGHT(C) MAXSI SOFTWARE, JONAS 'SORTIE' TERMANSEN 2008, 2009, 2010

    This file is part of Maxsi Engine.

    Maxsi Engine is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Maxsi Engine is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with Maxsi Engine. If not, see <http://www.gnu.org/licenses/>.

	*/ #include "MaxsiEngineContributors.h" /*

	If you modify this file, please enter your name below and provide contact
	information in MaxsiEngineContributors.h. For more information please see
	MaxsiEngineContributors.h.
	
	Contributors to this file:

	- Jonas 'Sortie' Termansen
	- [your name here]

	MaxsiEngine.dll
	A linkable version of Maxsi Engine

	Minidump.h
	Crash reporting

****************************************************************************/

#ifdef _MSC_VER

#include "dbghelp.h"
#include <time.h>

#define MAXIDUMP 0 // 1 = Full dump, 0 = partial dump (recommended).

namespace MaxsiEngine
{
	class MaxsiEngineMiniDumper;

	LINK type_ExceptionFilter ME_ExceptionFilter;

	bool ME_IsDataSectionNeeded( const WCHAR* pModuleName ) ;

	BOOL CALLBACK ME_MiniDumpCallback(
		PVOID                            pParam, 
		const PMINIDUMP_CALLBACK_INPUT   pInput, 
		PMINIDUMP_CALLBACK_OUTPUT        pOutput 
	);

	class dbghelp_dll : public DLL
	{
	public:	
		Declare_Class(dbghelp_dll, DLL);

		DECLARE_FUNCTION_WINAPI(BOOL,MiniDumpWriteDump,(
		HANDLE			hProcess,
		DWORD			dwPid,
		HANDLE			hFile,
		MINIDUMP_TYPE	DumpType,

		CONST PMINIDUMP_EXCEPTION_INFORMATION		ExceptionParam,
		CONST PMINIDUMP_USER_STREAM_INFORMATION		UserStreamParam,
		CONST PMINIDUMP_CALLBACK_INFORMATION		CallbackParam));

		virtual bool	Import	( const char* LibraryPath )
		{
			if ( BaseClass::Import(LibraryPath) )
			{
				IMPORT_FUNCTION(MiniDumpWriteDump);
				return true;
			}
			return false;
		}
	};

	class MaxsiEngineMiniDumper
	{
	public:
		MaxsiEngineMiniDumper(char* AppName);
		~MaxsiEngineMiniDumper();
	};

	static MESTR* ApplicationName;

	LONG WINAPI ExceptionFilter(__in struct _EXCEPTION_POINTERS *ExceptionInfo )
	{
		LONG Result		=	EXCEPTION_CONTINUE_SEARCH;

		// Get the date
		__int64		LocalTime;
		tm			CurrentTime;
		char		CurrentTime_str[26];
		errno_t		err;

		_time64( &LocalTime );

		err = _gmtime64_s( &CurrentTime, &LocalTime );
		if (err)
		{
			MESTR* Error = BuildString(1,_MESTR("An unhandled error occured in ") _MESTR(Application) _MESTR(". Maxsi Engine failed to generate a minidump file because _gmtime64_s failed. ") _MESTR(SupportId(31)));
			MessageBoxM(NULL,Error,_MESTR("Maxsi Engine"),MB_ICONERROR);
			delete[] Error;
			return Result;				
		}

		err = asctime_s(CurrentTime_str, 26, &CurrentTime);
		if (err)
		{
			char* Error = BuildString(1,_MESTR("An unhandled error occured in ") _MESTR(Application) _MESTR(". Maxsi Engine failed to generate a minidump file because asctime_s failed. ") _MESTR(SupportId(32)));
			MessageBoxM(NULL,Error,_MESTR("Maxsi Engine"),MB_ICONERROR);
			delete[] Error;
			return Result;		
		}

		// Now that we got the date, proceed and generate the filename

		char*		Dir		=	GetWorkingDirectory();
		char*		File	=	BuildString(5,Dir,Application,"_v" __DATE__ "_date",CurrentTime_str,".mdmp");
		str_replace(File+2,":","_"); // plus two to avoid c: becoming c_
		str_replace(File,"\n","_"); // asctime_s adds a line break? wut

		char*		DllFile	=	BuildString(2,Dir,"dbghelp.dll");

		// Load the dbghelp.dll
		
		dbghelp_dll* dll = new dbghelp_dll;

		if ( dll )
		{
			if ( !dll->Import(DllFile) )
			{
				dll->Import("dbghelp.dll"); // Perhaps the system has a proper copy
			}
			
			if (dll->Executable!=NULL)
			{
				// create the file
				HANDLE hFile = CreateFileA( File, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS,
											FILE_ATTRIBUTE_NORMAL, NULL );

				if (hFile!=INVALID_HANDLE_VALUE)
				{
					_MINIDUMP_EXCEPTION_INFORMATION ExInfo;

					ExInfo.ThreadId = ::GetCurrentThreadId();
					ExInfo.ExceptionPointers = ExceptionInfo;
					ExInfo.ClientPointers = NULL;

						
					MINIDUMP_CALLBACK_INFORMATION mci; 
#if MAXIDUMP
					mci.CallbackRoutine     = NULL;
#else
					mci.CallbackRoutine     = (MINIDUMP_CALLBACK_ROUTINE)ME_MiniDumpCallback;
#endif					
					mci.CallbackParam       = 0; 					

					MINIDUMP_TYPE mdt       = (MINIDUMP_TYPE)(
#if MAXIDUMP
							MiniDumpWithFullMemory |
#else
							MiniDumpWithPrivateReadWriteMemory | 
							MiniDumpWithDataSegs | 
#endif
							MiniDumpWithHandleData |
							MiniDumpWithFullMemoryInfo | 
							MiniDumpWithThreadInfo | 
							MiniDumpWithUnloadedModules ); 

					// write the dump
					BOOL bOK = dll->MiniDumpWriteDump( GetCurrentProcess(), GetCurrentProcessId(), hFile, mdt, &ExInfo, 0, &mci ); 

					if (bOK)
					{
						char* Error = BuildString(3,"An unhandled error occured in "Application". A minidump file containing information about how this crash happened has been saved to\n\n",File,"\n\nPlease send this file to the developers of this product. " SupportId(33));
						MessageBoxA(NULL,Error,"Maxsi Engine",MB_ICONERROR);
						delete[] Error;
						Result = EXCEPTION_EXECUTE_HANDLER;
					}
					else
					{				

						char* Error = BuildString(1,"An unhandled error occured in "Application". Maxsi Engine failed to generate a minidump file because something went wrong while writing the minidump. " SupportId(34));
						MessageBoxA(NULL,Error,"Maxsi Engine",MB_ICONERROR);
						delete[] Error;
					}
					CloseHandle(hFile);
				}
				else
				{
					char* Error = BuildString(3,"An unhandled error occured in "Application". Maxsi Engine failed to generate a minidump file because the target file path couldn't be opened\n\n",File, "\n\n" SupportId(35));
					MessageBoxA(NULL,Error,"Maxsi Engine",MB_ICONERROR);
					delete[] Error;
				}
			}
			else
			{
				char* Error = BuildString(1,"An unhandled error occured in "Application". Maxsi Engine failed to generate a minidump file because dbghelp.dll wasn't found. " SupportId(36));
				MessageBoxA(NULL,Error,"Maxsi Engine",MB_ICONERROR);
				delete[] Error;
			}
		}

		// Clean up
		delete[]	File;
		delete[]	Dir;
		delete[]	DllFile;
		delete		dll;
		return Result;
	}

	BOOL CALLBACK ME_MiniDumpCallback(
		PVOID                            pParam, 
		const PMINIDUMP_CALLBACK_INPUT   pInput, 
		PMINIDUMP_CALLBACK_OUTPUT        pOutput 
	) 
	{
		BOOL bRet = FALSE; 


		// Check parameters 

		if( pInput == 0 ) 
			return FALSE; 

		if( pOutput == 0 ) 
			return FALSE; 


		// Process the callbacks 

		switch( pInput->CallbackType ) 
		{
			case IncludeModuleCallback: 
			{
				// Include the module into the dump 
				bRet = TRUE; 
			}
			break; 

			case IncludeThreadCallback: 
			{
				// Include the thread into the dump 
				bRet = TRUE; 
			}
			break; 

			case ModuleCallback: 
			{
				// Are data sections available for this module ? 

				if( pOutput->ModuleWriteFlags & ModuleWriteDataSeg ) 
				{
					// Yes, they are, but do we need them? 

					if( !ME_IsDataSectionNeeded( pInput->Module.FullPath ) ) 
					{
						wprintf( L"Excluding module data sections: %s \n", pInput->Module.FullPath ); 

						pOutput->ModuleWriteFlags &= (~ModuleWriteDataSeg); 
					}
				}

				bRet = TRUE; 
			}
			break; 

			case ThreadCallback: 
			{
				// Include all thread information into the minidump 
				bRet = TRUE;  
			}
			break; 

			case ThreadExCallback: 
			{
				// Include this information 
				bRet = TRUE;  
			}
			break; 

			case MemoryCallback: 
			{
				// We do not include any information here -> return FALSE 
				bRet = FALSE; 
			}
			break; 

			case CancelCallback: 
				break; 
		}

		return bRet; 

	}


	bool ME_IsDataSectionNeeded( const WCHAR* pModuleName ) 
	{
		// Check parameters 

		if( pModuleName == 0 ) 
		{
			ME_UNDEFINED_BEHAVIOR("Parameter pModuleName is null in ME_IsDataSectionNeeded()!" ); 
			return false; 
		}

		// Extract the module name 

		WCHAR szFileName[_MAX_FNAME] = L""; 

		_wsplitpath_s( pModuleName, NULL, 0, NULL, 0, szFileName,_MAX_FNAME, NULL, 0 );

		// Compare the name with the list of known names and decide 

		// Remove all known system libraries
		if( _wcsicmp( szFileName, L"ntdll" ) == 0 ) 
		{
			return true; // Appears we should include this one
		}
		else if( _wcsicmp( szFileName, L"user32" ) == 0 ) 
		{
			return false;
		}
		else if( _wcsicmp( szFileName, L"gdi32" ) == 0 ) 
		{
			return false;
		}
		else if( _wcsicmp( szFileName, L"Ws2_32" ) == 0 ) 
		{
			return false;
		}
		else if( _wcsicmp( szFileName, L"zdll" ) == 0 ) 
		{
			return false;
		}
		else if( _wcsicmp( szFileName, L"comdlg32" ) == 0 ) 
		{
			return false;
		}
		else if( _wcsicmp( szFileName, L"ComCtl32" ) == 0 ) 
		{
			return false;
		}
		else if( _wcsicmp( szFileName, L"Mswsock" ) == 0 ) 
		{
			return false;
		}
		else if( _wcsicmp( szFileName, L"Ole32" ) == 0 ) 
		{
			return false;
		}
		else if( _wcsicmp( szFileName, L"shell32" ) == 0 ) 
		{
			return false;
		}
		else if( _wcsicmp( szFileName, L"Advapi32" ) == 0 ) 
		{
			return false;
		}

		// Complete 

		return true; // Assume we need any other modules.

	}


	MaxsiEngineMiniDumper::MaxsiEngineMiniDumper(char* AppName)
	{
		SetUnhandledExceptionFilter(MaxsiEngine::ExceptionFilter);
		ApplicationName		=	BuildString(1,AppName);
		ME_ExceptionFilter	=	ExceptionFilter;
	}

	MaxsiEngineMiniDumper::~MaxsiEngineMiniDumper()
	{
		if ( ApplicationName )
		{
			delete[] ApplicationName;
		}
	}	

	MaxsiEngine::MaxsiEngineMiniDumper MiniDumper(Application);

}

#endif